`
https://leetcode.cn/problems/sliding-window-maximum/
`

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var maxSlidingWindow = function (nums, k) {
  // 使用最大堆维护滑动窗口内的最大值 [value, index]
  const pq = new Heap((a, b) => b[0] - a[0])
  const res = []

  for (let i = 0; i < nums.length; i++) {
    pq.push([nums[i], i]);

    if (i >= k - 1) {
      // 移除不在窗口内的堆顶元素
      while (pq.peek()[1] <= i - k) {
        pq.pop();
      }
      res.push(pq.peek()[0]);
    }
  }

  return res
};

class Heap {
  constructor(compare) {
    this.heap = [];
    this.compare = compare;
  }
  size() {
    return this.heap.length;
  }
  isEmpty() {
    return this.heap.length === 0;
  }
  peek() {
    return this.heap[0] ?? undefined;
  }
  push(value) {
    this.heap.push(value);
    this.bubbleUp(this.heap.length - 1);
  }
  bubbleUp(index) {
    while (index > 0) {
      const parentIndex = Math.floor((index - 1) / 2);
      if (this.compare(this.heap[index], this.heap[parentIndex]) < 0) {
        [this.heap[index], this.heap[parentIndex]] = [this.heap[parentIndex], this.heap[index]];
        index = parentIndex;
      } else break;
    }
  }
  pop() {
    if (this.isEmpty()) return undefined;
    const top = this.heap[0];
    const last = this.heap.pop();
    if (!this.isEmpty()) {
      this.heap[0] = last;
      this.sinkDown(0);
    }
    return top;
  }
  sinkDown(index) {
    let current = index;
    const length = this.size();
    while (true) {
      const left = 2 * current + 1;
      const right = 2 * current + 2;
      let target = current;
      if (left < length && this.compare(this.heap[left], this.heap[target]) < 0)
        target = left;
      if (right < length && this.compare(this.heap[right], this.heap[target]) < 0)
        target = right;
      if (target !== current) {
        [this.heap[current], this.heap[target]] = [this.heap[target], this.heap[current]];
        current = target;
      } else break;
    }
  }
  update(oldValue, newValue) {
    const index = this.heap.indexOf(oldValue);
    if (index === -1) return false;
    this.heap[index] = newValue;
    this.bubbleUp(index);
    this.sinkDown(index);
    return true;
  }
  remove(value) {
    const index = this.heap.indexOf(value);
    if (index === -1) return false;
    const last = this.heap.pop();
    if (index === this.heap.length) return true;
    this.heap[index] = last;
    this.bubbleUp(index);
    this.sinkDown(index);
    return true;
  }
  contains(value) {
    return this.heap.includes(value);
  }
}